﻿using System;
using System.Linq;
using System.Runtime.InteropServices;

namespace Btstack
{
    public class Gatt
    {
        public const byte GATT_EVENT_QUERY_COMPLETE = 0xA0;
        public const byte GATT_EVENT_SERVICE_QUERY_RESULT = 0xA1;
        public const byte GATT_EVENT_CHARACTERISTIC_QUERY_RESULT = 0xA2;
        public const byte GATT_EVENT_INCLUDED_SERVICE_QUERY_RESULT = 0xA3;
        public const byte GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT = 0xA4;
        public const byte GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT = 0xA5;
        public const byte GATT_EVENT_LONG_CHARACTERISTIC_VALUE_QUERY_RESULT = 0xA6;
        public const byte GATT_EVENT_NOTIFICATION = 0xA7;
        public const byte GATT_EVENT_INDICATION = 0xA8;
        public const byte GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT = 0xA9;
        public const byte GATT_EVENT_LONG_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT = 0xAA;
        public const byte GATT_EVENT_MTU = 0xAB;
        public const byte GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE = 0xAC;

        public const ushort GATT_PRIMARY_SERVICE_UUID = 0x2800;
        public const ushort GATT_SECONDARY_SERVICE_UUID = 0x2801;
        public const ushort GATT_INCLUDE_SERVICE_UUID = 0x2802;
        public const ushort GATT_CHARACTERISTICS_UUID = 0x2803;
        public const ushort GATT_CHARACTERISTIC_EXTENDED_PROPERTIES = 0x2900;
        public const ushort GATT_CHARACTERISTIC_USER_DESCRIPTION = 0x2901;
        public const ushort GATT_CLIENT_CHARACTERISTICS_CONFIGURATION = 0x2902;
        public const ushort GATT_SERVER_CHARACTERISTICS_CONFIGURATION = 0x2903;
        public const ushort GATT_CHARACTERISTIC_PRESENTATION_FORMAT = 0x2904;
        public const ushort GATT_CHARACTERISTIC_AGGREGATE_FORMAT = 0x2905;
        public const ushort GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NONE = 0;
        public const ushort GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION = 1;
        public const ushort GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_INDICATION = 2;
        public const ushort GATT_CLIENT_ANY_CONNECTION = 0xffff;
        public const ushort GATT_CLIENT_ANY_VALUE_HANDLE = 0x0000;
        public const ushort GAP_SERVICE_UUID = 0x1800;
        public const ushort GAP_DEVICE_NAME_UUID = 0x2a00;
        public const ushort GAP_APPEARANCE_UUID = 0x2a01;
        public const ushort GAP_PERIPHERAL_PRIVACY_FLAG = 0x2a02;
        public const ushort GAP_RECONNECTION_ADDRESS_UUID = 0x2a03;
        public const ushort GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS_UUID = 0x2a04;
        public const ushort GAP_SERVICE_CHANGED = 0x2a05;

        [StructLayout(LayoutKind.Sequential)]
        public readonly struct ClientServiceType
        {
            public readonly ushort StartGroupHandle;
            public readonly ushort EndGroupHandle;
            public readonly ushort uuid16;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public readonly byte[] uuid128;
        }

        [StructLayout(LayoutKind.Sequential)]
        public readonly struct ClientCharacteristicType
        {
            public readonly ushort StartHandle;
            public readonly ushort ValueHandle;
            public readonly ushort endHandle;
            public readonly ushort properties;
            public readonly ushort uuid16;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public readonly byte[] uuid128;
        }

        [StructLayout(LayoutKind.Sequential)]
        public readonly struct ClientNotificationType
        {
            public readonly IntPtr item;
            public readonly Btstack.PacketHandlerType callback;
            public readonly ushort ConHandle;
            public readonly ushort AttributeHandle;
        }

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_init")]
        public static extern void ClientInit();

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_discover_primary_services_by_uuid16")]
        private static extern byte ClientDiscoverPrimaryServicesByUuid16(Btstack.PacketHandlerType callback, ushort ConHandle, ushort uuid16);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_discover_characteristics_for_service_by_uuid16")]
        private static extern byte ClientDiscoverCharacteristicsForServiceByUuid16(Btstack.PacketHandlerType callback, ushort ConHandle, IntPtr service, ushort uuid16);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_deserialize_service")]
        private static extern void ClientDeserializeService(IntPtr packet, int offset, IntPtr service);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_deserialize_characteristic")]
        private static extern byte ClientDeserializeCharacteristic(IntPtr packet, int offset, IntPtr characteristic);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_listen_for_characteristic_value_updates")]
        private static extern void ClientListenForCharacteristicValueUpdates(IntPtr notification, Btstack.PacketHandlerType PacketHandler, ushort ConHandle, IntPtr characteristic);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_write_client_characteristic_configuration")]
        private static extern byte ClientWriteClientCharacteristicConfiguration(Btstack.PacketHandlerType callback, ushort ConHandle, IntPtr characteristic, ushort configuration);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_stop_listening_for_characteristic_value_updates")]
        private static extern void ClientStopListeningForCharacteristicValueUpdates(IntPtr notification);

        [DllImport("btstack-c.dll", EntryPoint = "gatt_client_write_value_of_characteristic_without_response")]
        private static extern byte ClientWriteValueOfCharacteristicWithoutResponse(ushort ConHandle, ushort ValueHandle, ushort ValueLength, IntPtr value);

        public static byte ClientDiscoverPrimaryServicesByUuid16(Action<byte, ushort, byte[]> callback, ushort ConHandle, ushort uuid16)
        {
            void CallbackLowlevel(byte type, ushort channel, IntPtr PacketPtr, ushort size)
            {
                byte[] packet = new byte[size];
                Marshal.Copy(PacketPtr, packet, 0, size);
                callback(type, channel, packet);
            }

            return ClientDiscoverPrimaryServicesByUuid16(CallbackLowlevel, ConHandle, uuid16);
        }

        public static void EventServiceQueryResultGetService(byte[] packet, out ClientServiceType service)
        {
            IntPtr ServicePtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientServiceType>());

            IntPtr PacketPtr = Marshal.AllocHGlobal(packet.Length);
            Marshal.Copy(packet, 0, PacketPtr, packet.Length);

            ClientDeserializeService(PacketPtr, 4, ServicePtr);

            service = (ClientServiceType)Marshal.PtrToStructure(ServicePtr, typeof(ClientServiceType));

            Marshal.FreeHGlobal(PacketPtr);
        }

        public static void EventCharacteristicQueryResultGetCharacteristic(byte[] packet, out ClientCharacteristicType characteristic)
        {
            IntPtr characteristicPtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientCharacteristicType>());

            IntPtr PacketPtr = Marshal.AllocHGlobal(packet.Length);
            Marshal.Copy(packet, 0, PacketPtr, packet.Length);

            ClientDeserializeCharacteristic(PacketPtr, 4, characteristicPtr);

            characteristic = (ClientCharacteristicType)Marshal.PtrToStructure(characteristicPtr, typeof(ClientCharacteristicType));

            Marshal.FreeHGlobal(PacketPtr);
        }

        public static byte ClientDiscoverCharacteristicsForServiceByUuid16(Action<byte, ushort, byte[]> callback, ushort ConHandle, ClientServiceType service, ushort uuid16)
        {
            IntPtr ServicePtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientServiceType>());
            Marshal.StructureToPtr(service, ServicePtr, false);

            void CallbackLowlevel(byte type, ushort channel, IntPtr PacketPtr, ushort size)
            {
                byte[] packet = new byte[size];
                Marshal.Copy(PacketPtr, packet, 0, size);
                callback(type, channel, packet);
            }

            byte ret = ClientDiscoverCharacteristicsForServiceByUuid16(CallbackLowlevel, ConHandle, ServicePtr, uuid16);

            Marshal.FreeHGlobal(ServicePtr);

            return ret;
        }

        public static void ClientListenForCharacteristicValueUpdates(out ClientNotificationType notification, Action<byte, ushort, byte[]> PacketHandler, ushort ConHandle, ClientCharacteristicType characteristic)
        {
            void CallbackLowlevel(byte type, ushort channel, IntPtr PacketPtr, ushort size)
            {
                byte[] packet = new byte[size];
                Marshal.Copy(PacketPtr, packet, 0, size);
                PacketHandler(type, channel, packet);
            }

            IntPtr ClientNotificationPtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientNotificationType>());

            IntPtr CharacteristicPtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientCharacteristicType>());
            Marshal.StructureToPtr(characteristic, CharacteristicPtr, false);

            ClientListenForCharacteristicValueUpdates(ClientNotificationPtr, CallbackLowlevel, ConHandle, CharacteristicPtr);

            notification = (ClientNotificationType)Marshal.PtrToStructure(ClientNotificationPtr, typeof(ClientNotificationType));

            Marshal.FreeHGlobal(CharacteristicPtr);
        }

        public static void ClientStopListeningForCharacteristicValueUpdates(ClientNotificationType notification)
        {
            IntPtr NotificationPtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientNotificationType>());
            Marshal.StructureToPtr(notification, NotificationPtr, false);

            ClientStopListeningForCharacteristicValueUpdates(NotificationPtr);

            Marshal.FreeHGlobal(NotificationPtr);
        }

        public static byte ClientWriteClientCharacteristicConfiguration(Action<byte, ushort, byte[]> callback, ushort ConHandle, ClientCharacteristicType characteristic, ushort configuration)
        {
            void CallbackLowlevel(byte type, ushort channel, IntPtr PacketPtr, ushort size)
            {
                byte[] packet = new byte[size];
                Marshal.Copy(PacketPtr, packet, 0, size);
                callback(type, channel, packet);
            }

            IntPtr CharacteristicPtr = Marshal.AllocHGlobal(Marshal.SizeOf<ClientCharacteristicType>());
            Marshal.StructureToPtr(characteristic, CharacteristicPtr, false);

            var ret = ClientWriteClientCharacteristicConfiguration(CallbackLowlevel, ConHandle, CharacteristicPtr, configuration);

            Marshal.FreeHGlobal(CharacteristicPtr);

            return ret;
        }

        public static byte EventQueryCompleteGetAttStatus(byte[] packet)
        {
            return packet[4];
        }

        public static byte[] EventNotificationGetValue(byte[] packet)
        {
            int length = BitConverter.ToUInt16(packet, 6);
            return packet.Skip(8).Take(length).ToArray();
        }

        public static byte ClientWriteValueOfCharacteristicWithoutResponse(ushort ConHandle, ushort ValueHandle, byte[] value)
        {
            IntPtr ValuePtr = Marshal.AllocHGlobal(value.Length);
            Marshal.Copy(value, 0, ValuePtr, value.Length);

            var ret = ClientWriteValueOfCharacteristicWithoutResponse(ConHandle, ValueHandle, (ushort)value.Length, ValuePtr);

            return ret;
        }
    }
}
